﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using HengTong.Model.Db.Hr.Attendance;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using TiaoTaoHelper.Wtm.Services;
using WalkingTec.Mvvm.Core;
using WalkingTec.Mvvm.Core.Support.Quartz;

namespace HengTong.Tasks;

/// <summary>
/// 考勤计算定时任务(性能优化版)
/// </summary>
[Display(Name = "考勤计算", Description = "根据打卡记录和排班表计算考勤结果")]
[QuartzRepeat(3600 * 4, 0, true, 60 * 1)] // 每4小时执行一次,延迟5分钟启动
public class CalculateAttendanceTask : BaseTask<CalculateAttendanceTask>
{
    protected override async Task<bool> ExecuteStart()
    {
        try
        {
            var startTime = DateTime.Now;
            Logger.LogInformation($"开始计算考勤 - {startTime:yyyy-MM-dd HH:mm:ss}");

            var dc = Wtm.DC;

            var twoDaysAgo = DateOnly.FromDateTime(DateTime.Now.AddDays(-3));
            var startDate = twoDaysAgo.AddDays(-30);
            var endDate = twoDaysAgo;

            // 批量获取需要计算的排班记录
            var records = await dc.Set<SysAttendanceRecord>()
                .AsNoTracking() // 只读查询,减少内存开销
                .Include(x => x.Person)
                .Include(x => x.AttendanceShiftGroup)
                .ThenInclude(x => x.Shifts)
                .ThenInclude(x => x.Shift)
                .Where(x => x.ShiftDate >= startDate && x.ShiftDate <= endDate)
                .Where(x => x.ShiftType == ShiftTypeEnum.正常上班)
                .Where(x => x.Status == ShiftStatusEnum.正常)
                .Where(x => x.AttendanceResult == AttendanceResultEnum.未计算 || x.IsAbnormal)
                .ToListAsync();

            if (!records.Any())
            {
                Logger.LogInformation("没有需要计算的考勤记录");
                return true;
            }

            // 提取所有人员ID和时间范围(处理可空类型)
            var personIds = records
                .Where(x => x.PersonId.HasValue)
                .Select(x => x.PersonId.Value)
                .Distinct()
                .ToList();

            if (!personIds.Any())
            {
                Logger.LogWarning("没有有效的人员ID");
                return true;
            }

            var minDate = records.Min(x => x.ShiftDate).ToDateTime(new TimeOnly(0, 0, 0)).AddDays(-1);
            var maxDate = records.Max(x => x.ShiftDate).ToDateTime(new TimeOnly(23, 59, 59)).AddDays(1);

            // 批量获取所有相关的打卡记录
            var allRawRecords = await dc.Set<SysAttendanceRawRecord>()
                .AsNoTracking()
                .Where(x => x.PersonId.HasValue && personIds.Contains(x.PersonId.Value))
                .Where(x => x.PunchTime >= minDate && x.PunchTime <= maxDate)
                .Where(x => x.ProcessStatus == "未处理")
                .OrderBy(x => x.PersonId)
                .ThenBy(x => x.PunchTime)
                .ToListAsync();

            // 按人员ID分组(处理可空类型)
            var rawRecordsByPerson = allRawRecords
                .Where(x => x.PersonId.HasValue)
                .GroupBy(x => x.PersonId.Value)
                .ToDictionary(g => g.Key, g => g.ToList());

            int calculatedCount = 0;
            int abnormalCount = 0;
            var processedRawRecords = new List<SysAttendanceRawRecord>();

            // 分批处理,避免内存溢出
            const int batchSize = 500;
            for (int i = 0; i < records.Count; i += batchSize)
            {
                var batch = records.Skip(i).Take(batchSize).ToList();

                foreach (var record in batch)
                {
                    try
                    {
                        var shiftGroup = record.AttendanceShiftGroup;
                        if (shiftGroup?.Shifts == null || !shiftGroup.Shifts.Any())
                        {
                            Logger.LogWarning($"排班记录{record.ID}找不到排班组或班次信息");
                            continue;
                        }

                        // **关键修复: 先判断是否为休息日**
                        var shouldWork = ShouldWorkOnDate(record.ShiftDate, shiftGroup);

                        if (!shouldWork)
                        {
                            // 休息日,标记为正常
                            record.AttendanceResult = AttendanceResultEnum.正常;
                            record.IsAbnormal = false;
                            record.LateMinutes = 0;
                            record.EarlyLeaveMinutes = 0;
                            record.MissingPunchCount = 0;
                            record.WorkMinutes = 0;

                            //Logger.LogDebug($"人员{record.Person?.Name}, 日期{record.ShiftDate}为休息日,跳过考勤计算");
                            calculatedCount++;
                            continue;
                        }

                        // 从内存字典中获取该员工的打卡记录
                        if (!record.PersonId.HasValue)
                        {
                            Logger.LogWarning($"排班记录{record.ID}的人员ID为空");
                            continue;
                        }

                        var personRawRecords = GetPersonDayPunchRecords(
                            rawRecordsByPerson,
                            record.PersonId.Value,
                            record.ShiftDate);

                        if (!personRawRecords.Any())
                        {
                            // **工作日**无打卡记录,标记为旷工
                            record.AttendanceResult = AttendanceResultEnum.旷工;
                            record.IsAbnormal = true;
                            record.MissingPunchCount = shiftGroup.Shifts.Count * 2;
                            record.WorkMinutes = 0;
                            abnormalCount++;
                        }
                        else
                        {
                            // 按班次分别计算考勤
                            var processedRecords = CalculateAttendanceByShifts(
                                record,
                                shiftGroup,
                                personRawRecords);

                            processedRawRecords.AddRange(processedRecords);

                            if (record.IsAbnormal)
                            {
                                abnormalCount++;
                            }
                        }

                        calculatedCount++;
                    }
                    catch (Exception ex)
                    {
                        Logger.LogError(ex, $"计算排班记录{record.ID}时发生错误");
                    }
                }


                // 批量更新数据库
                await UpdateBatchRecordsAsync(dc, batch, processedRawRecords);
                processedRawRecords.Clear();
            }

            var endTime = DateTime.Now;
            var duration = (endTime - startTime).TotalSeconds;

            Logger.LogInformation($"考勤计算完成 - 执行时间: {duration:F2}秒, " +
                                  $"计算记录数: {calculatedCount}/{records.Count}, " +
                                  $"异常记录数: {abnormalCount}");

            return true;
        }
        catch (Exception e)
        {
            Logger.LogError(e, "考勤计算时发生错误");
            return false;
        }
    }

    /// <summary>
    /// 从内存中获取指定员工指定日期的打卡记录
    /// </summary>
    private List<SysAttendanceRawRecord> GetPersonDayPunchRecords(
        Dictionary<Guid, List<SysAttendanceRawRecord>> rawRecordsByPerson,
        Guid personId,
        DateOnly shiftDate)
    {
        if (!rawRecordsByPerson.TryGetValue(personId, out var personRecords))
        {
            return new List<SysAttendanceRawRecord>();
        }

        var dayStart = shiftDate.ToDateTime(new TimeOnly(0, 0, 0)).AddDays(-1);
        var dayEnd = shiftDate.ToDateTime(new TimeOnly(23, 59, 59)).AddDays(1);

        return personRecords
            .Where(x => x.PunchTime >= dayStart && x.PunchTime <= dayEnd)
            .ToList();
    }

    /// <summary>
    /// 批量更新数据库(减少数据库交互次数)
    /// </summary>
    private async Task UpdateBatchRecordsAsync(
        IDataContext dc,
        List<SysAttendanceRecord> records,
        List<SysAttendanceRawRecord> processedRawRecords)
    {
        // 更新考勤记录
        foreach (var record in records)
        {
            dc.UpdateEntity(record);
        }

        // 更新打卡记录处理状态
        var rawRecordIds = processedRawRecords.Select(x => x.ID).Distinct().ToList();
        if (rawRecordIds.Any())
        {
            var rawRecordsToUpdate = await dc.Set<SysAttendanceRawRecord>()
                .Where(x => rawRecordIds.Contains(x.ID))
                .ToListAsync();

            foreach (var raw in rawRecordsToUpdate)
            {
                raw.ProcessStatus = "已处理";
                raw.ProcessTime = DateTime.Now;
            }
        }

        await dc.SaveChangesAsync();
    }

    /// <summary>
    /// 按班次分别计算考勤(优化版,返回已处理的打卡记录)
    /// </summary>
    private List<SysAttendanceRawRecord> CalculateAttendanceByShifts(
        SysAttendanceRecord record,
        SysAttendanceShiftGroup shiftGroup,
        List<SysAttendanceRawRecord> allRawRecords)
    {

        // 初始化汇总数据
        record.LateMinutes = 0;
        record.EarlyLeaveMinutes = 0;
        record.MissingPunchCount = 0;
        record.WorkMinutes = 0;
        record.IsAbnormal = false;

        var processedRecords = new List<SysAttendanceRawRecord>();
        var attendanceResults = new List<AttendanceResultEnum>();

        // 按班次顺序计算
        var sortedShifts = shiftGroup.Shifts
            .OrderBy(x => x.Shift.WorkStartDayOffset)
            .ThenBy(x => x.Shift.WorkStartTime)
            .ToList();

        foreach (var shiftAndGroup in sortedShifts)
        {
            var shift = shiftAndGroup.Shift;

            // 计算班次时间范围
            var workStartDateTime = CalculateDateTime(record.ShiftDate, shift.WorkStartDayOffset, shift.WorkStartTime);
            var workEndDateTime = CalculateDateTime(record.ShiftDate, shift.WorkEndDayOffset, shift.WorkEndTime);

            var punchStartWindowStart = CalculateDateTime(record.ShiftDate, shift.WorkStartPunchStartDayOffset,
                shift.WorkStartPunchStartTime);
            var punchStartWindowEnd = CalculateDateTime(record.ShiftDate, shift.WorkStartPunchEndDayOffset,
                shift.WorkStartPunchEndTime);
            var punchEndWindowStart = CalculateDateTime(record.ShiftDate, shift.WorkEndPunchStartDayOffset,
                shift.WorkEndPunchStartTime);
            var punchEndWindowEnd = CalculateDateTime(record.ShiftDate, shift.WorkEndPunchEndDayOffset,
                shift.WorkEndPunchEndTime);

            // 找到该班次对应的打卡记录
            var availableRecords = allRawRecords.Except(processedRecords).ToList();

            var startPunch = availableRecords
                .FirstOrDefault(x => x.PunchTime >= punchStartWindowStart && x.PunchTime <= punchStartWindowEnd);

            var endPunch = availableRecords
                .LastOrDefault(x => x.PunchTime >= punchEndWindowStart && x.PunchTime <= punchEndWindowEnd);

            // 标记已使用的打卡记录
            if (startPunch != null)
            {
                processedRecords.Add(startPunch);
            }

            if (endPunch != null && endPunch != startPunch)
            {
                processedRecords.Add(endPunch);
            }

            // 计算该班次的考勤结果
            var shiftResult = CalculateSingleShiftResult(
                shift,
                workStartDateTime,
                workEndDateTime,
                startPunch?.PunchTime,
                endPunch?.PunchTime,
                out int lateMinutes,
                out int earlyMinutes,
                out int missingCount,
                out int workMinutes,
                out bool isAbnormal);

            // 累加到总记录
            record.LateMinutes = (record.LateMinutes ?? 0) + lateMinutes;
            record.EarlyLeaveMinutes = (record.EarlyLeaveMinutes ?? 0) + earlyMinutes;
            record.MissingPunchCount = (record.MissingPunchCount ?? 0) + missingCount;
            record.WorkMinutes = (record.WorkMinutes ?? 0) + workMinutes;
            record.IsAbnormal = record.IsAbnormal || isAbnormal;

            attendanceResults.Add(shiftResult);
        }

        // 确定最终的考勤结果
        if (attendanceResults.Contains(AttendanceResultEnum.旷工))
        {
            record.AttendanceResult = AttendanceResultEnum.旷工;
        }
        else if (attendanceResults.Contains(AttendanceResultEnum.迟到且早退))
        {
            record.AttendanceResult = AttendanceResultEnum.迟到且早退;
        }
        else if (attendanceResults.Contains(AttendanceResultEnum.迟到) &&
                 attendanceResults.Contains(AttendanceResultEnum.早退))
        {
            record.AttendanceResult = AttendanceResultEnum.迟到且早退;
        }
        else if (attendanceResults.Contains(AttendanceResultEnum.迟到))
        {
            record.AttendanceResult = AttendanceResultEnum.迟到;
        }
        else if (attendanceResults.Contains(AttendanceResultEnum.早退))
        {
            record.AttendanceResult = AttendanceResultEnum.早退;
        }
        else if (record.MissingPunchCount > 0)
        {
            record.AttendanceResult = AttendanceResultEnum.缺卡;
        }
        else
        {
            record.AttendanceResult = AttendanceResultEnum.正常;
        }

        return processedRecords;
    }


    /// <summary>
    /// 判断指定日期是否应该上班(完整修复版)
    /// </summary>
    private bool ShouldWorkOnDate(DateOnly date, SysAttendanceShiftGroup shiftGroup)
    {
        if (shiftGroup.Type == ShiftGroupType.行政班)
        {
            if (!TiaoTaoHelper.Extension.String.StringExtension.TryJsonParse(
                    shiftGroup.SchedulePattern, out List<string> days))
            {
                Logger.LogWarning($"班组{shiftGroup.ID}排班模式格式错误");
                return false;
            }

            var dayOfWeek = date.DayOfWeek;
            var dayName = dayOfWeek switch
            {
                DayOfWeek.Monday => "星期一",
                DayOfWeek.Tuesday => "星期二",
                DayOfWeek.Wednesday => "星期三",
                DayOfWeek.Thursday => "星期四",
                DayOfWeek.Friday => "星期五",
                DayOfWeek.Saturday => "星期六",
                DayOfWeek.Sunday => "星期天",
                _ => ""
            };

            return days.Contains(dayName);
        }
        else if (shiftGroup.Type == ShiftGroupType.三班倒)
        {
            if (!TiaoTaoHelper.Extension.String.StringExtension.TryJsonParse(
                    shiftGroup.SchedulePattern,
                    out HengTong.ViewModel.Hr.SysAttendanceShiftGroupVms.ShiftGroupDayInfo info))
            {
                Logger.LogWarning($"班组{shiftGroup.ID}排班模式格式错误");
                return false;
            }

            if (info.WorkDate == null)
            {
                Logger.LogWarning($"班组{shiftGroup.ID}排班模式错误,缺少排班起始日期");
                return false;
            }

            var daysDiff = (date.ToDateTime(TimeOnly.MinValue) -
                            info.WorkDate.Value.ToDateTime(TimeOnly.MinValue)).Days;
            var cycleIndex = ((daysDiff % 3) + 3) % 3;

            SysAttendanceShiftTag currentTag = cycleIndex switch
            {
                0 => SysAttendanceShiftTag.A,
                1 => SysAttendanceShiftTag.B,
                2 => SysAttendanceShiftTag.休,
                _ => throw new Exception("计算三班倒错误")
            };

            // 关键修复: 休息标签直接返回false
            if (currentTag == SysAttendanceShiftTag.休)
            {
                return false;
            }

            // 检查是否有对应标签的工作班次
            return shiftGroup.Shifts.Any(s => s.Tag == currentTag);
        }
        else if (shiftGroup.Type == ShiftGroupType.两班倒)
        {
            if (!TiaoTaoHelper.Extension.String.StringExtension.TryJsonParse(
                    shiftGroup.SchedulePattern,
                    out HengTong.ViewModel.Hr.SysAttendanceShiftGroupVms.ShiftGroupDayInfo info))
            {
                Logger.LogWarning($"班组{shiftGroup.ID}排班模式格式错误");
                return false;
            }

            if (info.WorkDate == null)
            {
                Logger.LogWarning($"班组{shiftGroup.ID}排班模式错误,缺少排班起始日期");
                return false;
            }

            var daysDiff = (date.ToDateTime(TimeOnly.MinValue) -
                            info.WorkDate.Value.ToDateTime(TimeOnly.MinValue)).Days;
            var cycleIndex = ((daysDiff % 2) + 2) % 2;

            SysAttendanceShiftTag currentTag = cycleIndex == 0
                ? SysAttendanceShiftTag.A
                : SysAttendanceShiftTag.休;

            // 关键修复: 休息标签直接返回false
            return currentTag != SysAttendanceShiftTag.休;
        }
        else if (shiftGroup.Type == ShiftGroupType.上一休二)
        {
            if (!TiaoTaoHelper.Extension.String.StringExtension.TryJsonParse(
                    shiftGroup.SchedulePattern,
                    out HengTong.ViewModel.Hr.SysAttendanceShiftGroupVms.ShiftGroupDayInfo info))
            {
                Logger.LogWarning($"班组{shiftGroup.ID}排班模式格式错误");
                return false;
            }

            if (info.WorkDate == null)
            {
                Logger.LogWarning($"班组{shiftGroup.ID}排班模式错误,缺少排班起始日期");
                return false;
            }

            var daysDiff = (date.ToDateTime(TimeOnly.MinValue) -
                            info.WorkDate.Value.ToDateTime(TimeOnly.MinValue)).Days;
            var cycleIndex = ((daysDiff % 3) + 3) % 3;

            SysAttendanceShiftTag currentTag = cycleIndex switch
            {
                0 => SysAttendanceShiftTag.A,
                1 => SysAttendanceShiftTag.休1,
                2 => SysAttendanceShiftTag.休2,
                _ => throw new Exception("计算上一休二错误")
            };

            // 关键修复: 只有A标签才需要上班
            return currentTag == SysAttendanceShiftTag.A;
        }

        // 未知班组类型
        Logger.LogWarning($"班组{shiftGroup.ID}类型未知: {shiftGroup.Type}");
        return false;
    }


    /// <summary>
    /// 计算单个班次的考勤结果
    /// </summary>
    private AttendanceResultEnum CalculateSingleShiftResult(
        SysAttendanceShift shift,
        DateTime workStartTime,
        DateTime workEndTime,
        DateTime? actualStartTime,
        DateTime? actualEndTime,
        out int lateMinutes,
        out int earlyMinutes,
        out int missingCount,
        out int workMinutes,
        out bool isAbnormal)
    {
        lateMinutes = 0;
        earlyMinutes = 0;
        missingCount = 0;
        workMinutes = 0;
        isAbnormal = false;

        // 检查缺卡
        if (!actualStartTime.HasValue)
        {
            missingCount++;
            isAbnormal = true;
        }

        if (!actualEndTime.HasValue)
        {
            missingCount++;
            isAbnormal = true;
        }

        // 如果启用了违规规则
        if (shift.AttendanceViolationRuleEnabled)
        {
            // 计算迟到
            if (actualStartTime.HasValue && actualStartTime.Value > workStartTime)
            {
                var late = (int)(actualStartTime.Value - workStartTime).TotalMinutes;
                if (late >= shift.LateMinutesThreshold)
                {
                    lateMinutes = late;
                    isAbnormal = true;

                    // 检查是否达到旷工标准
                    if (late >= shift.AbsenceByLateMinutesThreshold)
                    {
                        return AttendanceResultEnum.旷工;
                    }
                }
            }

            // 计算早退
            if (actualEndTime.HasValue && actualEndTime.Value < workEndTime)
            {
                var early = (int)(workEndTime - actualEndTime.Value).TotalMinutes;
                if (early >= shift.EarlyLeaveMinutesThreshold)
                {
                    earlyMinutes = early;
                    isAbnormal = true;

                    // 检查是否达到旷工标准
                    if (early >= shift.AbsenceByEarlyLeaveMinutesThreshold)
                    {
                        return AttendanceResultEnum.旷工;
                    }
                }
            }

            // 检查合计偏差是否达到旷工标准
            var totalDeviation = lateMinutes + earlyMinutes;
            if (totalDeviation >= shift.AbsenceByTotalDeviationMinutesThreshold)
            {
                return AttendanceResultEnum.旷工;
            }
        }

        // 计算工作时长
        if (actualStartTime.HasValue && actualEndTime.HasValue)
        {
            workMinutes = (int)(actualEndTime.Value - actualStartTime.Value).TotalMinutes;
            workMinutes = Math.Max(0, workMinutes);
        }
        else if (missingCount == 2)
        {
            // 完全缺卡，不计工时
            workMinutes = 0;
        }
        else
        {
            // 部分缺卡，使用标准工时
            workMinutes = shift.StandardWorkMinutes;
        }

        // 确定考勤结果
        if (lateMinutes > 0 && earlyMinutes > 0)
        {
            return AttendanceResultEnum.迟到且早退;
        }
        else if (lateMinutes > 0)
        {
            return AttendanceResultEnum.迟到;
        }
        else if (earlyMinutes > 0)
        {
            return AttendanceResultEnum.早退;
        }
        else if (missingCount > 0)
        {
            return AttendanceResultEnum.缺卡;
        }

        return AttendanceResultEnum.正常;
    }

    /// <summary>
    /// 计算日期时间
    /// </summary>
    private DateTime CalculateDateTime(DateOnly baseDate, SysDayOffset dayOffset, TimeSpan time)
    {
        var date = baseDate.AddDays((int)dayOffset);
        return date.ToDateTime(TimeOnly.FromTimeSpan(time));
    }
}